package net.sunbeta.test.util;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;

import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.collections.Predicate;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

/**
 * 
 Usually to update some entities in database I use service that must be
 * calculate changes from new and old object and update changes in
 * database(using DAO, for example). And the common task is update collections.
 * I wrote simple class that calculate changes from old and new collections and
 * create three collections: to delete, to update and to create. You can see
 * class below and some description for it
 * 
 * @author beams
 * 
 * @param <T>
 */
public class ChangesCollections<T> {
	private static class CollectionFactory<T> {
		private static final Log log = LogFactory.getLog(CollectionFactory.class);
		private Class<? extends Collection> classCollection = ArrayList.class;

		private CollectionFactory(Class<? extends Collection> classCollection) {
			this.classCollection = classCollection;
		}

		@SuppressWarnings("unchecked")
		public Collection<T> getNewCollection() {
			try {
				return classCollection.newInstance();
			} catch (Exception e) {
				String warnInfo = "Can not create collection for class [%s], create java.util.ArrayList";
				log.warn(String.format(warnInfo, classCollection.getName()),e);
				return new ArrayList<T>();
			}
		}
	}

	private Collection<T> deleteCollection;
	private Collection<T> addCollection;
	private Collection<T> updateCollection;

	public ChangesCollections(Collection<T> oldCollection, Collection<T> newCollection) {
		CollectionFactory<T> factory = new CollectionFactory<T>(oldCollection.getClass());

		this.deleteCollection = factory.getNewCollection();
		this.addCollection = factory.getNewCollection();
		this.updateCollection = factory.getNewCollection();

		addCollection.addAll(newCollection);
		deleteCollection.addAll(oldCollection);
		Iterator<T> addIt = addCollection.iterator();
		while (addIt.hasNext()) {
			T addObject = addIt.next();
			if (contains(oldCollection, addObject)) {
				updateCollection.add(addObject);
				deleteCollection.remove(addObject);
				addIt.remove();
			}
		}
	}

	public Collection<T> getDeleteCollection() {
		return deleteCollection;
	}

	public Collection<T> getAddCollection() {
		return addCollection;
	}

	public Collection<T> getUpdateCollection() {
		return updateCollection;
	}

	private boolean contains(Collection<T> collection, T object) {
		if (object.getClass().isAssignableFrom(Predicate.class)) {
			return CollectionUtils.exists(collection, (Predicate) object);
		}

		return collection.contains(object);
	}
	
	public static void main(String[] args) {
		List<String> newList = new ArrayList<String>();
		newList.add("a");
		newList.add("d");
		newList.add("e");
		newList.add("f");
		
		List<String> oldList = new ArrayList<String>();
		oldList.add("a");
		oldList.add("b");
		oldList.add("c");
		oldList.add("d");
		
		ChangesCollections<String> testCollections = new ChangesCollections<String>(oldList, newList);
		System.out.println("--------add items--------");
		System.out.println(testCollections.getAddCollection());
		System.out.println("--------del items--------");
		System.out.println(testCollections.getDeleteCollection());
		System.out.println("--------upt items--------");
		System.out.println(testCollections.getUpdateCollection());

	}
}
